"""Unit test for singleton.py"""

import singleton
import unittest

_VERBOSE = True
def verbose( *args ):
    if _VERBOSE:
        print ' '.join( [ str(i) for i in args ] )

class _Base(unittest.TestCase):
    class TestKeySingleton( singleton.KeySingleton ):
        '''
        Class used to test singleton.KeySingleton class.
        ( It is not allowed to instanciate singleton.KeySingleton without
        defining an __init__ slot )
        '''
        def __init__( self, a, b=2, c=3, test='test' ):
            verbose(
                "#--------- NEW TestKeySingleton", id(self), a, b, c, test 
            )            
        
    def __init__( self, *args, **kwargs ):
        unittest.TestCase.__init__( self, *args, **kwargs )
    
    def get_count(self):
        '''
        Return the number of singleton objects.
        '''
        return len( singleton.KeySingleton._singles.keys() )
    
    def assertCountEqual( self, requestedCount ):
        currentCount = self.get_count()
        verbose( "#--- COUNT:", currentCount )
        self.assertEqual( currentCount, requestedCount )

class KeySingleton_GetInitArgs( _Base ):
    class TestClass:
        def __init__( self, a, b, c='Default', d='D' ):
            print "NEW TestClass", a, b, c

    def test_no_kw( self ):
        self.assertEqual(
            (1, 2, 3, 4),
            singleton.KeySingleton._GetInitArgs(
                self.TestClass,
                1, 2, 3, 4
            )
        )
    
    def test_kw_only( self ):
        self.assertEqual(
            (1, 2, 3, 4),
            singleton.KeySingleton._GetInitArgs(
                self.TestClass,
                a=1, b=2, c=3, d=4
            )
        )
        
    def test_kw_only_disorder( self ):
        self.assertEqual(
            (1, 2, 3, 4),
            singleton.KeySingleton._GetInitArgs(
                self.TestClass,
                d=4, b=2, a=1, c=3
            )
        )
        
    def test_default( self ):
        self.assertEqual(
            (1, 2, 'Default', 'D'),
            singleton.KeySingleton._GetInitArgs(
                self.TestClass,
                1, 2
            )
        )
        
    def test_mixed( self ):
        self.assertEqual(
            (1, 2, 'Default', 4),
            singleton.KeySingleton._GetInitArgs(
                self.TestClass,
                1, 2, d=4
            )
        )
        
    def test_missing( self ):
        self.assertRaises(
            KeyError, 
            singleton.KeySingleton._GetInitArgs,
            self.TestClass, 1, d=4
        )
        
    def test_vararg_raise( self ):
        class Fail( singleton.KeySingleton ):
            def __init__( self, *args ):
                pass 
        self.assertRaises(
            SyntaxError, 
            Fail,
            1, 2, 3
        )
        
    def test_varkw_raise( self ):
        class Fail( singleton.KeySingleton ):
            def __init__( self, **kwargs ):
                pass 
        self.assertRaises(
            SyntaxError, 
            Fail,
            a=1, b=2, c=3
        )
        
    def test_varargs_varkw_raise( self ):
        class Fail( singleton.KeySingleton ):
            def __init__( self, *args, **kwargs ):
                pass 
        self.assertRaises(
            SyntaxError, 
            Fail,
            1, 2, c=3, d=4
        )
        
class KeySingleton_WeakRefs( _Base ):
    '''Test memory not leaking thanks to weakref.'''
    def test_weak_garbaged( self ):
        '''
        Check that singletons out of scope are garbage collected.
        '''
        def scoped_creation():
            singles = [ self.TestKeySingleton(i) for i in range( 100 ) ]

        self.assertCountEqual( 0 )
        scoped_creation()
        self.assertCountEqual( 0 )
    
    def test_weak_alive( self ):
        '''
        Check that singletons still exists if referenced.
        '''
        def scoped_creation():
            singles = [ self.TestKeySingleton(i) for i in range( 100 ) ]
            return singles
        
        self.assertCountEqual( 0 )
        singles = scoped_creation()
        self.assertCountEqual( 100 )

class KeySingleton_Keys( _Base ):
    '''Test the KeySingleton._SingleKey override.'''
    @classmethod
    def FirstArgKey( klass, *args ):
        '''
        Possible override for KeySingleton._SingleKey
        Uses the first arg only and no keyword arg.
        '''
        return args[:1]
        
    @classmethod
    def NoneKey( klass, *args ):
        '''
        Possible override for KeySingleton._SingleKey
        Returns None, wich desactivate the singletonization.
        '''
        return None
        
    def test_default_key_same( self ):
        '''
        Two instances with the same init args must be the same object.
        '''
        a = self.TestKeySingleton( 1, 2, 3, test=4 )
        b = self.TestKeySingleton( 1, 2, 3, test=4 )

        self.assertCountEqual( 1 )
        self.assertEqual( id(a), id(b) )
        
    def test_default_key_different( self ):
        '''
        Two instances with different init args must be different objects.
        '''
        a = self.TestKeySingleton( 1, 2, 3, test=4 )
        b = self.TestKeySingleton( 5, 6, 7, test=8 )

        self.assertCountEqual( 2 )
        self.assertNotEqual( id(a), id(b) )

    def test_default_key_different_class_same( self ):
        '''
        Two instances of two subclasses with the same init args must be
        the same object.
        '''
        class A( singleton.KeySingleton ):
            def __init__( self, a, b, c, test ):
                singleton.KeySingleton.__init__( self )
                verbose( "#--------- NEW A", id(self), a, b, c, test )
        class B( singleton.KeySingleton ):
            def __init__( self, a, b, c, test ):
                singleton.KeySingleton.__init__( self )
                # this wont be printed since a will already exists.
                # NB: this is happens only will different klasses :/
                verbose( "#--------- NEW B", id(self), a, b, c, test )

        a = A( 1, 2, 3, test=4 )
        b = B( 1, 2, 3, test=4 )
        
        self.assertCountEqual( 1 )
        self.assertEqual( id(a), id(b) )
        

    def test_FirstArg_key_same( self ):
        '''
        Using self.FirstArgKey as _SingleKey, two instances with the same first
        init arg but different other args must be the same objects.
        
        The singleton __init__ is called at each instanciation try.
        '''
        class TestFirstArgKey( singleton.KeySingleton ):
            def __init__( self, a, b, c, test ):
                singleton.KeySingleton.__init__( self )
                verbose( "#--------- NEW TestFirstArgKey", id(self), a, b, c, test )
                self.args = (a, b, c, test)
        TestFirstArgKey._SingleKey = self.FirstArgKey
        
        a = TestFirstArgKey( 1, 2, 3, test=4 )
        b = TestFirstArgKey( 1, 6, 7, test=8 )
        
        self.assertCountEqual( 1 )
        self.assertEqual( id(a), id(b) )
        
    def test_FirstArg_key_different( self ):
        '''
        Using self.NoneKey as _SingleKey, two instances with different first 
        init args but same other args must be different objects.
        '''
        class TestFirstArgKey( singleton.KeySingleton ):
            def __init__( self, a, b, c, test ):
                singleton.KeySingleton.__init__( self )
                verbose( "#--------- NEW TestFirstArgKey", id(self), a, b, c, test )
        TestFirstArgKey._SingleKey = self.FirstArgKey
        
        a = TestFirstArgKey( 1, 2, 3, test=4 )
        b = TestFirstArgKey( 5, 2, 4, test=4 )

        self.assertCountEqual( 2 )
        self.assertNotEqual( id(a), id(b) )

    def test_None_key_same( self ):
        '''
        Using self.NoneKey as _SingleKey, two instances with the same init
        args must be different objects.
        '''
        class TestNoneKey( singleton.KeySingleton ):
            def __init__( self, a, b, c, test ):
                singleton.KeySingleton.__init__( self )
                verbose( "#--------- NEW TestNoneKey", id(self), a, b, c, test )
        TestNoneKey._SingleKey = self.NoneKey
        
        a = TestNoneKey( 1, 2, 3, test=4 )
        b = TestNoneKey( 1, 2, 3, test=4 )
        
        self.assertCountEqual( 0 ) # None key are not stored
        self.assertNotEqual( id(a), id(b) )
        
    def test_None_key_different( self ):
        '''
        Using self.NoneKey as _SingleKey, two instances with different init
        args must be different objects.
        '''
        class TestNoneKey( singleton.KeySingleton ):
            def __init__( self, a, b, c, test ):
                singleton.KeySingleton.__init__( self )
                verbose( "#--------- NEW TestNoneKey", id(self), a, b, c, test )
        TestNoneKey._SingleKey = self.NoneKey
        
        a = TestNoneKey( 1, 2, 3, test=4 )
        b = TestNoneKey( 5, 6, 7, test=8 )

        self.assertCountEqual( 0 ) # None key are not stored
        self.assertNotEqual( id(a), id(b) )


if __name__ == "__main__":
    import sys
    if '-v' in sys.argv or '-q' in sys.argv:
        _VERBOSE = False
    unittest.main()


